// Copyright 2019 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef SRC_DAWN_NATIVE_FORMAT_H_
#define SRC_DAWN_NATIVE_FORMAT_H_

#include <array>
#include <variant>

#include "dawn/native/dawn_platform.h"

#include "dawn/common/TypedInteger.h"
#include "dawn/common/ityp_array.h"
#include "dawn/common/ityp_bitset.h"
#include "dawn/native/EnumClassBitmasks.h"
#include "dawn/native/Error.h"
#include "dawn/native/Subresource.h"

#include "absl/strings/str_format.h"

// About multi-planar formats.
//
// Dawn supports additional multi-planar formats when the multiplanar-formats extension is enabled.
// When enabled, Dawn treats planar data as sub-resources (ie. 1 sub-resource == 1 view == 1 plane).
// A multi-planar format name encodes the channel mapping and order of planes. For example,
// R8BG8Biplanar420Unorm is YUV 4:2:0 where Plane 0 = R8, and Plane 1 = BG8.
//
// Requirements:
// * Plane aspects cannot be combined with color, depth, or stencil aspects.
// * Only compatible multi-planar formats of planes can be used with multi-planar texture
// formats.
// * Can't access multiple planes without creating per plane views (no color conversion).
// * Multi-planar format cannot be written or read without a per plane view.
//
// TODO(dawn:551): Consider moving this comment.

namespace dawn::native {

enum class Aspect : uint8_t;
class DeviceBase;

// This mirrors wgpu::TextureSampleType as a bitmask instead.
enum class SampleTypeBit : uint8_t {
    None = 0x0,
    Float = 0x1,
    UnfilterableFloat = 0x2,
    Depth = 0x4,
    Sint = 0x8,
    Uint = 0x10,
};

// Converts a wgpu::TextureSampleType to its bitmask representation.
SampleTypeBit SampleTypeToSampleTypeBit(wgpu::TextureSampleType sampleType);

struct TexelBlockInfo {
    uint32_t byteSize;
    uint32_t width;
    uint32_t height;
};

enum class TextureComponentType {
    Float,
    Sint,
    Uint,
};

struct RequiresFeature {
    wgpu::FeatureName feature;
};

struct CompatibilityMode {};

using UnsupportedReason =
    std::variant</* is supported */ std::monostate, RequiresFeature, CompatibilityMode>;

struct AspectInfo {
    TexelBlockInfo block;
    TextureComponentType baseType{};
    SampleTypeBit supportedSampleTypes{};
    wgpu::TextureFormat format = wgpu::TextureFormat::Undefined;
};

// The number of formats Dawn knows about. Asserts in BuildFormatTable ensure that this is the
// exact number of known format.
static constexpr uint32_t kKnownFormatCount = 95;

using FormatIndex = TypedInteger<struct FormatIndexT, uint32_t>;

struct Format;
using FormatTable = ityp::array<FormatIndex, Format, kKnownFormatCount>;

// A wgpu::TextureFormat along with all the information about it necessary for validation.
struct Format {
    wgpu::TextureFormat format = wgpu::TextureFormat::Undefined;

    static const UnsupportedReason supported;

    // TODO(crbug.com/dawn/1332): These members could be stored in a Format capability matrix.
    bool isRenderable = false;
    bool isCompressed = false;
    // A format can be known but not supported because it is part of a disabled extension.
    UnsupportedReason unsupportedReason;
    bool supportsStorageUsage = false;
    bool supportsMultisample = false;
    bool supportsResolveTarget = false;
    Aspect aspects{};
    // Only used for renderable color formats:
    uint8_t componentCount = 0;                  // number of color channels
    uint8_t renderTargetPixelByteCost = 0;       // byte cost of pixel in render targets
    uint8_t renderTargetComponentAlignment = 0;  // byte alignment for components in render targets

    bool IsSupported() const;
    bool IsColor() const;
    bool HasDepth() const;
    bool HasStencil() const;
    bool HasDepthOrStencil() const;
    bool HasAlphaChannel() const;
    bool IsSnorm() const;

    // IsMultiPlanar() returns true if the format allows selecting a plane index. This is only
    // allowed by multi-planar formats (ex. NV12).
    bool IsMultiPlanar() const;

    const AspectInfo& GetAspectInfo(wgpu::TextureAspect aspect) const;
    const AspectInfo& GetAspectInfo(Aspect aspect) const;

    // The index of the format in the list of all known formats: a unique number for each format
    // in [0, kKnownFormatCount)
    FormatIndex GetIndex() const;

    // baseFormat represents the memory layout of the format.
    // If two formats has the same baseFormat, they could copy to and be viewed as the other
    // format. Currently two formats have the same baseFormat if they differ only in sRGB-ness.
    wgpu::TextureFormat baseFormat = wgpu::TextureFormat::Undefined;

    // Returns true if the formats are copy compatible.
    // Currently means they differ only in sRGB-ness.
    bool CopyCompatibleWith(const Format& otherFormat) const;

    // Returns true if the formats are texture view format compatible.
    // Currently means they differ only in sRGB-ness.
    bool ViewCompatibleWith(const Format& otherFormat) const;

  private:
    // Used to store the aspectInfo for one or more planes. For single plane "color" formats,
    // only the first aspect info or aspectInfo[0] is valid. For depth-stencil, the first aspect
    // info is depth and the second aspect info is stencil. For multi-planar formats,
    // aspectInfo[i] is the ith plane.
    std::array<AspectInfo, kMaxPlanesPerFormat> aspectInfo{};

    friend FormatTable BuildFormatTable(const DeviceBase* device);
};

class FormatSet : public ityp::bitset<FormatIndex, kKnownFormatCount> {
    using Base = ityp::bitset<FormatIndex, kKnownFormatCount>;

  public:
    using Base::Base;
    using Base::operator[];

    bool operator[](const Format& format) const;
    typename Base::reference operator[](const Format& format);
};

// Implementation details of the format table in the device.

// Returns the index of a format in the FormatTable.
FormatIndex ComputeFormatIndex(wgpu::TextureFormat format);
// Builds the format table with the extensions enabled on the device.
FormatTable BuildFormatTable(const DeviceBase* device);

absl::FormatConvertResult<absl::FormatConversionCharSet::kString> AbslFormatConvert(
    const UnsupportedReason& value,
    const absl::FormatConversionSpec& spec,
    absl::FormatSink* s);

}  // namespace dawn::native

namespace dawn {

template <>
struct IsDawnBitmask<dawn::native::SampleTypeBit> {
    static constexpr bool enable = true;
};

}  // namespace dawn

#endif  // SRC_DAWN_NATIVE_FORMAT_H_
